Solutions/Threat Intelligence (NEW)/Analytic Rules/DomainEntity_SecurityAlert.yaml (67 lines of code) (raw):

id: df88b403-1cb9-49ea-a43d-b6613051cf7f name: TI map Domain entity to SecurityAlert description: | 'Identifies a match in SecurityAlert table from any Domain IOC from TI' severity: Medium requiredDataConnectors: - connectorId: ThreatIntelligence dataTypes: - ThreatIntelligenceIndicator - connectorId: ThreatIntelligenceTaxii dataTypes: - ThreatIntelligenceIndicator - connectorId: MicrosoftCloudAppSecurity dataTypes: - SecurityAlert - connectorId: AzureSecurityCenter dataTypes: - SecurityAlert - connectorId: MicrosoftDefenderThreatIntelligence dataTypes: - ThreatIntelligenceIndicator queryFrequency: 1h queryPeriod: 14d triggerOperator: gt triggerThreshold: 0 tactics: - CommandAndControl relevantTechniques: - T1071 query: | let dt_lookBack = 1h; let ioc_lookBack = 14d; let SecurityAlerts = SecurityAlert | where TimeGenerated > ago(dt_lookBack) | extend domain = todynamic(dynamic_to_json(extract_all(@"(((xn--)?[a-z0-9\-]+\.)+([a-z]+|(xn--[a-z0-9]+)))", dynamic([1]), tolower(Entities)))) | where isnotempty(domain) | mv-expand domain | extend domain = tostring(domain) | extend EntitiesDynamicArray = parse_json(Entities) | mv-apply EntitiesDynamicArray on (summarize HostName = take_anyif(tostring(EntitiesDynamicArray.HostName), EntitiesDynamicArray.Type == "host"), IP_addr = take_anyif(tostring(EntitiesDynamicArray.Address), EntitiesDynamicArray.Type == "ip") ) | extend Alert_TimeGenerated = TimeGenerated | extend Alert_Description = Description; let AlertDomains = SecurityAlerts | distinct domain | summarize make_list(domain); let Domain_Indicators = materialize(ThreatIntelIndicators | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0))) | where isnotempty(IndicatorType) and IndicatorType == "domain-name" | extend DomainName = tolower(ObservableValue) | where isnotempty(DomainName) | where TimeGenerated >= ago(ioc_lookBack) | extend TI_DomainEntity = tolower(DomainName) | where TI_DomainEntity in (AlertDomains) | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id | where IsActive == true and ValidUntil > now() | extend Description = tostring(parse_json(Data).description) | where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"); Domain_Indicators // Using innerunique to keep performance fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated | join kind=innerunique (SecurityAlerts) on $left.TI_DomainEntity == $right.domain | where Alert_TimeGenerated < ValidUntil | summarize Alert_TimeGenerated = arg_max(Alert_TimeGenerated, *) by Id, AlertName | extend ActivityGroupNames = extract("ActivityGroup:([^,]+)", 1, tostring(parse_json(Data).labels)) | extend Url = DomainName | project Alert_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, ConfidenceScore, DomainName, AlertName, Alert_Description, ProviderName, AlertSeverity, ConfidenceLevel, HostName, IP_addr, Url, Entities, Type, TI_DomainEntity | extend timestamp = Alert_TimeGenerated | project-reorder *, ConfidenceScore, DomainName, AlertName, Alert_TimeGenerated, ProviderName, AlertSeverity, Entities, Type, TI_DomainEntity, timestamp entityMappings: - entityType: Host fieldMappings: - identifier: HostName columnName: HostName - entityType: IP fieldMappings: - identifier: Address columnName: IP_addr - entityType: URL fieldMappings: - identifier: Url columnName: Url version: 1.4.3 kind: Scheduled